
local Language = import("./Language/Language.lua");
local Block = import("./Block.lua");
local ShadowBlock = import("./ShadowBlock.lua");
local ToolBox = import("./ToolBox.lua");
local ScrollBar = import("./ScrollBar.lua");
local Editor = import("./Editor/init.lua");
local ContextMenu = import("./ContextMenu.lua");
local Blockly = commonlib.inherit(commonlib.getfield("Window.Elements.Element"));

Blockly:Property("ClassName", "Blockly");
Blockly:Property("UnitSize", 3);              -- 单元格大小
Blockly:Property("ToolBoxWidth", 300);        -- 工具栏宽度
Blockly:Property("ToolBox");                  -- 工具栏
Blockly:Property("Language");                 -- 语言
Blockly:Property("MouseCaptureUI");           -- 捕获鼠标UI
Blockly:Property("DraggingBlock");            -- 当前拖拽块
Blockly:Property("ShadowBlock");              -- 占位块
Blockly:Property("Editor");                   -- 编辑器
Blockly:Property("ContextMenu");              -- 上下文菜单

function Blockly:ctor()
    self:SetName("Blockly");
    self:SetShadowBlock(ShadowBlock:new():Init(self));
    self:SetToolBox(ToolBox:new():Init(self));

    self.__all_block_map__, self.__all_category_list__, self.__all_category_map__ = {}, {}, {};
    self.__block_list__ = {};
    self.__offset_x_unit_count__, self.__offset_y_unit_count__ = 1000000, 1000000;
    self.__offset_x__, self.__offset_y__ = self.__offset_x_unit_count__ * self:GetUnitSize(), self.__offset_y_unit_count__ * self:GetUnitSize();
    self.__content_left_unit_count__, self.__content_top_unit_count__, self.__content_right_unit_count__, self.__content_bottom_unit_count__ = 0, 0, 0, 0;
    self.__horizontal_scroll_bar__ = ScrollBar:new():Init(self, "horizontal");
    self.__vertical_scroll_bar__ = ScrollBar:new():Init(self, "vertical");
end

function Blockly:Init(xmlnode, window, parent)
    Blockly._super.InitElement(self, xmlnode, window, parent);

    self:OnAttrValueChange("Language");
    self:OnAttrValueChange("ToolBoxXmlText");
    self:OnAttrValueChange("XmlText");

    local editor = Editor:new():Init(self);
    self:SetEditor(editor);
    self:AddChildren(editor);

    local contextmenu = ContextMenu:new():Init(self);
    self:SetContextMenu(contextmenu);
    self:AddChildren(contextmenu);
    return self;
end


function Blockly:OnAttrValueChange(attrName, attrValue)
    if (attrName == "Language") then
        self:SetLanguage(self:GetAttrStringValue("Language", "scratch"));
        local language = Language[self:GetLanguage()];
        if (language) then
            self.__all_block_map__ = language.__all_block_map__;
            self.__all_category_list__ = language.__all_category_list__;
            self.__all_category_map__ = language.__all_category_map__;
            for _, category in ipairs(self.__all_category_list__) do
                if (category.color) then category.color = self:ToColorFromString(category.color) end 
            end
            for _, category in pairs(self.__all_category_map__) do
                if (category.color) then category.color = self:ToColorFromString(category.color) end 
            end
            self:GetToolBox():SetLanguage(language);
        end
    end
    
    if (attrName == "ToolBoxXmlText") then
        self:GetToolBox():SetXmlText(self:GetAttrStringValue("ToolBoxXmlText", ""));  
        -- self:GetToolBox():SetXmlText(self:GetAttrStringValue("ToolBoxXmlText", [[
        --     <category name="motion">
        --         <block type="Move"></block>
        --         <block type="TurnRight"></block>
        --     </category>
        -- ]]));
    end

    if (attrName == "XmlText") then
        local xmltext = self:GetAttrStringValue("XmlText", "");
--         local xmltext = self:GetAttrStringValue("XmlText", [==[
-- <blockly __offset_x__="0" __offset_y__="0">
--     <block __top_unit_count__="79" __left_unit_count__="223" __type__="MoveTo">
--         <field name="pos">
--             <value><![CDATA[random_position]]></value>
--         </field>
--         <block __top_unit_count__="91" __left_unit_count__="223" __type__="Say">
--             <input name="text">
--                 <value><![CDATA[你好!]]></value>
--             </input>
--             <input name="time">
--                 <value><![CDATA[2]]></value>
--             </input>
--             <block __top_unit_count__="103" __left_unit_count__="223" __type__="Move">
--                 <input name="step">
--                     <block __top_unit_count__="105" __left_unit_count__="236" __type__="X"/>
--                 </input>
--             </block>
--         </block>
--     </block>
-- </blockly>
--         ]==]);
        self:LoadFromXmlNodeText(xmltext);
    end
end


function Blockly:NewBlock(block_type)
    if (not block_type or not self.__all_block_map__[block_type]) then return nil end 
    local opt = self.__all_block_map__[block_type];
    local block = Block:new():Init(self, opt);
    block:SetColor(self.__all_category_map__[opt.category].color or block:GetColor());
    return block;
end

-- 获取块索引
function Blockly:GetBlockIndex(block)
    for index, __block__ in ipairs(self.__block_list__) do
        if (__block__ == block) then return index end
    end
    return;
end

-- 获取块列表
function Blockly:GetBlockList()
    return self.__block_list__;
end

-- 移除块
function Blockly:AddBlock(block, isHeadBlock)
    if (not block) then return end
    local index = self:GetBlockIndex(block);
    if (not index) then 
        if (isHeadBlock) then
            return table.insert(self.__block_list__, 1, block);
        else
            return table.insert(self.__block_list__, block); 
        end
    end
    
    local head, tail = 1, #self.__block_list__;
    if (isHeadBlock) then
        self.__block_list__[head], self.__block_list__[index] = self.__block_list__[index], self.__block_list__[head];  -- 放置头部
    else
        self.__block_list__[tail], self.__block_list__[index] = self.__block_list__[index], self.__block_list__[tail];  -- 放置尾部
    end
end

-- 添加块
function Blockly:RemoveBlock(block)
    local index = self:GetBlockIndex(block);
    if (not index) then return end
    table.remove(self.__block_list__, index);
end

-- 更新布局
function Blockly:UpdateLayout()
    Blockly._super.UpdateLayout(self);

    local _, _, w, h = self:GetPosition();
    self:GetToolBox():SetWidthHeight(self:GetToolBoxWidth(), h);

    self:OnWorkspaceChange();
end

-- 渲染
function Blockly:RenderContent()
    local x, y, w, h = self:GetPosition();

    self:Save();
    self:DrawRectangle(x, y, w, h, false, false);
    self:Clip();
    self:Translate(x, y);
    self:ApplyStyleFontToPaint();
    self:SetLineWidth(1);
    self:SetPenColor(0xff);

    -- 绘制工作区图块
    self:Translate(-self.__offset_x__, -self.__offset_y__);
    local __dragging_block__ = self:GetDraggingBlock();
    for _, block in ipairs(self.__block_list__) do
        if (__dragging_block__ ~= block) then
            block:Render();
        end
    end
    self:Translate(self.__offset_x__, self.__offset_y__);

    -- 绘制工具栏
    self:GetToolBox():Render();

    -- 当前块最后绘制
    self:Translate(-self.__offset_x__, -self.__offset_y__);
    if (__dragging_block__) then 
        __dragging_block__:Render();
        -- draw delete icon 
        local left, top = __dragging_block__:GetLeftTop();
        local mouse_x, mouse_y = self:ScreenXYToElementXY(self:GetMouseXY());
        if (self:IsInnerDeleteArea(mouse_x, mouse_y)) then
            local delete_icon_size = 4;
            self:SetPenColor(0xff0000ff);
            self:SetLineWidth(2);
            self:DrawLine(left - delete_icon_size, top - delete_icon_size, left + delete_icon_size, top + delete_icon_size);
            self:DrawLine(left - delete_icon_size, top + delete_icon_size, left + delete_icon_size, top - delete_icon_size);
        end
    end 
    self:Translate(self.__offset_x__, self.__offset_y__);

    -- scrollbar render
    self.__horizontal_scroll_bar__:Render();
    self.__vertical_scroll_bar__:Render();

    self:Translate(-x, -y);
    self:SetPenColor(0x000000ff);
    self:DrawRectangle(x, y, w, h, false, true);
    self:Restore();
end

function Blockly:OnMouseDown(event)
    local x, y = self:ScreenXYToElementXY(event:GetXY());
    local ui = self:GetMouseUI(x, y);
    -- 捕获鼠标
    self:CaptureMouse();
    self:SetMouseCaptureUI(ui);

    -- 隐藏编辑器
    self:GetEditor():Hide();

    -- 目标元素处理事件
    if (ui) then return ui:OnMouseDown(event) end 
    
    -- blockly 处理事件
    self.__is_mouse_down__ = true;
    self.__start_offset_x__, self.__start_offset_y__ = self.__offset_x__, self.__offset_y__;
    self.__start_mouse_x__, self.__start_mouse_y__ = event:GetXY();
end

function Blockly:OnMouseMove(event)
    if (self.__is_mouse_down__ and event:IsLeftButtonPressed()) then
        local x, y = event:GetXY();
        local UnitSize = self:GetUnitSize();
        self.__offset_x__ = self.__start_offset_x__ - (x - self.__start_mouse_x__);
        self.__offset_y__ = self.__start_offset_y__ - (y - self.__start_mouse_y__);
        self.__offset_x_unit_count__ = math.floor(self.__offset_x__ / UnitSize);
        self.__offset_y_unit_count__ = math.floor(self.__offset_y__ / UnitSize);
        if (#self.__block_list__ == 0) then
            self.__offset_x_unit_count__ = math.max(self.__offset_x_unit_count__, 0);
            self.__offset_y_unit_count__ = math.max(self.__offset_y_unit_count__, 0);
        else
            self.__offset_x_unit_count__ = math.min(math.max(self.__offset_x_unit_count__, self.__min_offset_x_count__), self.__max_offset_x_count__);
            self.__offset_y_unit_count__ = math.min(math.max(self.__offset_y_unit_count__, self.__min_offset_y_count__), self.__max_offset_y_count__);
        end
        self.__offset_x__ = self.__offset_x_unit_count__ * UnitSize;
        self.__offset_y__ = self.__offset_y_unit_count__ * UnitSize;
        self:OnWorkspaceChange();
        -- print(self.__offset_x_unit_count__, self.__offset_y_unit_count__)
        -- print(self.__min_offset_x_count__, self.__max_offset_x_count__, self.__min_offset_y_count__, self.__max_offset_y_count__)
        return;
    end

    local x, y = self:ScreenXYToElementXY(event:GetXY());
    local ui = self:GetMouseUI(x, y);
    if (ui) then return ui:OnMouseMove(event) end 
end

function Blockly:OnMouseUp(event)
    if (self.__is_mouse_down__) then
        self.__is_mouse_down__ = false;
    end

    local x, y = self:ScreenXYToElementXY(event:GetXY());
    local ui = self:GetMouseUI(x, y);
    -- 释放鼠标
    self:ReleaseMouse();
    self:SetMouseCaptureUI(nil);

    if (ui) then return ui:OnMouseUp(event) end 

    if (event:IsLeftButton()) then
        self:OnClick(event);
    else
        self:OnContextMenu(event);
    end
end

function Blockly:OnMouseWheel(event)
    local x, y = self:ScreenXYToElementXY(event:GetXY());
    if (x < self:GetToolBoxWidth()) then
        return self:GetToolBox():OnMouseWheel(event);
    end

    local delta = event:GetMouseWheelDelta();
    self.__offset_y_unit_count__ = math.min(math.max(math.floor((self.__offset_y__ + delta) / self:GetUnitSize()), self.__min_offset_y_count__), self.__max_offset_y_count__);
    self.__offset_y__ = self.__offset_y_unit_count__ * self:GetUnitSize();
    self.__vertical_scroll_bar__:OnScroll();
end

function Blockly:OnClick(event)
end

function Blockly:OnContextMenu(event)
    local x, y = self:ScreenXYToElementXY(event:GetXY());
    local contextmenu = self:GetContextMenu();
    contextmenu:Show({
        {"导出工作区(XML)", "export_workspace_xml"},
        {"导入工作区(XML)", "import_workspace_xml"},
        {"生成代码(code)", "export_workspace_code"},
    }, {x, y}, self);
end

function Blockly:OnContextMenuItem(menuitem, contextmenu)
    local cmd = menuitem[2];
    if (cmd == "export_workspace_xml") then
        local xmltext = self:SaveToXmlNodeText();
        commonlib.SetClipboardText(xmltext);
        commonlib.Tip("工作区XML已拷贝至剪切板");
    elseif (cmd == "import_workspace_xml") then
        local xmltext = commonlib.GetClipboardText();
        self:LoadFromXmlNodeText(xmltext);
    elseif (cmd == "export_workspace_code") then
        local codetext = self:GenerateCode();
        commonlib.SetClipboardText(codetext);
        commonlib.Tip("图块代码已拷贝至剪切板");
    end
end

function Blockly:LogicXYToViewXY(x, y)
    return x - self.__offset_x__, y - self.__offset_y__;
end

function Blockly:ViewXYToLogicXY(x, y)
    return self.__offset_x__ + x, self.__offset_y__ + y;
end

function Blockly:GetMouseUI(x, y)
    -- mouse capture
    local ui = self:GetMouseCaptureUI();
    if (ui) then return ui end 
    
    -- toolbox
    ui = self:GetToolBox():GetMouseUI(x, y);
    if (ui) then return ui end 

    ui = self.__horizontal_scroll_bar__:GetMouseUI(x, y);
    if (ui) then return ui end 

    ui = self.__vertical_scroll_bar__:GetMouseUI(x, y);
    if (ui) then return ui end 

    -- workspace
    local logic_x, logic_y = x + self.__offset_x__, y + self.__offset_y__;
    local block_list_size = #self.__block_list__;
    for i = block_list_size, 1, -1 do
        local block = self.__block_list__[i];
        local ui = block:GetMouseUI(logic_x, logic_y);
        if (ui) then return ui end 
    end

    return nil;
end

function Blockly:IsInnerDeleteArea(x, y)
    local _, _, w, h = self:GetPosition();
    if (x < 0 or x > w or y < 0 or y > h) then return false end
    if (x < self:GetToolBoxWidth()) then return true end  
    return false;
end

function Blockly:NewBlockByXmlNode(xmlnode)
    if (xmlnode.name ~= "block") then return end
    local attr = xmlnode.attr or {};
    local block = self:NewBlock(xmlnode.attr and xmlnode.attr.__type__);
    if (not block) then return nil end 
    block:LoadFromXmlNode(xmlnode);
    return block;
end

function Blockly:SaveToXmlNode()
    local xmlnode = {name = "blockly", attr = {}};
    local attr = xmlnode.attr;
    attr.__offset_x__ = self.__offset_x__;
    attr.__offset_y__ = self.__offset_y__;

    for _, block in ipairs(self.__block_list__) do
        local subXmlNode = block:SaveToXmlNode();
        if (subXmlNode) then table.insert(xmlnode, subXmlNode) end 
    end

    return xmlnode;
end

function Blockly:LoadFromXmlNode(xmlnode)
    if (xmlnode.name ~= "blockly") then return end 

    local attr = xmlnode.attr or {};

    self.__block_list__ = {};
    self.__offset_x__ = tonumber(attr.__offset_x__) or 0;
    self.__offset_y__ = tonumber(attr.__offset_y__) or 0;

    for _, subXmlNode in ipairs(xmlnode) do
        local block = self:NewBlockByXmlNode(subXmlNode);
        if (block) then 
            block:SetLeftTopUnitCount(block:GetLeftTopUnitCount());
            block:UpdateLayout();
            table.insert(self.__block_list__, block);
        end 
    end
end

function Blockly:SaveToXmlNodeText()
    return commonlib.XmlEncode(self:SaveToXmlNode());
end

function Blockly:LoadFromXmlNodeText(xmltext)
    local xmlnode = commonlib.XmlDecode(xmltext);
    if (not xmlnode) then return end
    xmlnode = xmlnode.name and xmlnode or xmlnode[1];
    self:LoadFromXmlNode(xmlnode);
end

function Blockly:GenerateCode()
    local allcode = "";
    for _, block in ipairs(self.__block_list__) do
        local code = block:GetAllCode();
        allcode = allcode .. code;
    end
    print(allcode)
    return allcode;
end

function Blockly:GetWidthHeight() 
    local _, _, w, h = self:GetPosition();
    return w, h;
end

function Blockly:OnWorkspaceChange()
    self:AdjustWorkspaceSize();
    self.__horizontal_scroll_bar__:OnScroll();
    self.__vertical_scroll_bar__:OnScroll();
end

function Blockly:AdjustWorkspaceSize()
    local width, height = self:GetWidthHeight();
    local UnitSize = self:GetUnitSize();
    local ToolBoxWidth = self:GetToolBoxWidth();
    self.__toolbox_unit_count__ = math.floor(ToolBoxWidth / UnitSize);
    self.__width_unit_count__, self.__height_unit_count__ = math.ceil(width / UnitSize), math.ceil(height / UnitSize);
    self.__offset_x_unit_count__, self.__offset_y_unit_count__ = math.floor(self.__offset_x__ / UnitSize), math.floor(self.__offset_y__ / UnitSize);
    local left, top, right, bottom = nil, nil, nil, nil;
    local content_offset, max_block_width_unit_count, max_block_height_unit_count = 20, 0, 0;
    for _, block in ipairs(self.__block_list__) do 
        local leftUnitCount, topUnitCount = block:GetLeftTopUnitCount();
        local widthUnitCount, heightUnitCount = block:GetWidthHeightUnitCount();
        local rightUnitCount, bottomUnitCount = leftUnitCount + widthUnitCount, topUnitCount + heightUnitCount;
        left = left and math.min(left, leftUnitCount) or leftUnitCount;
        top = top and math.min(top, topUnitCount) or topUnitCount;
        right = right and math.max(right, rightUnitCount) or rightUnitCount;
        bottom = bottom and math.max(bottom, bottomUnitCount) or bottomUnitCount;
        max_block_width_unit_count = math.max(max_block_width_unit_count, widthUnitCount);
        max_block_height_unit_count = math.max(max_block_height_unit_count, heightUnitCount);
    end
    max_block_width_unit_count, max_block_height_unit_count = 0, 0;
    self.__content_left_unit_count__, self.__content_top_unit_count__, self.__content_right_unit_count__, self.__content_bottom_unit_count__ = left or 0, top or 0, right or 0, bottom or 0;
    self.__min_offset_x_count__ = math.max(0, self.__content_left_unit_count__ - (self.__width_unit_count__ - content_offset - max_block_width_unit_count));
    self.__max_offset_x_count__ = math.max(0, self.__content_right_unit_count__ - (self.__toolbox_unit_count__ + content_offset + max_block_width_unit_count));
    self.__min_offset_y_count__ = math.max(0, self.__content_top_unit_count__ - (self.__height_unit_count__ - content_offset - max_block_height_unit_count));
    self.__max_offset_y_count__ = math.max(0, self.__content_bottom_unit_count__ - (content_offset + max_block_height_unit_count));
end

return Blockly;


